package com.github.zyf.ioaj.algorithm.swarmintelligence;

import com.github.zyf.ioaj.algorithm.Input;
import com.github.zyf.ioaj.algorithm.Output;
import com.github.zyf.ioaj.annotation.internal.Algorithm;
import com.github.zyf.ioaj.annotation.internal.BasicParam;
import com.github.zyf.ioaj.annotation.internal.TempParam;
import com.github.zyf.ioaj.constant.FileType;
import com.github.zyf.ioaj.constant.ParamType;
import com.github.zyf.ioaj.export.Report;
import com.github.zyf.ioaj.export.Tracker;
import com.github.zyf.ioaj.utils.basedata.ArrayUtils;

/**
 * <h1>Artificial Fish Swarm Algorithm</h1>
 * <p>人工鱼群算法</p>
 * <p>默认求解最小值，如果需要求解最大值，将目标函数乘以-1即可，最后得到的值再次乘以-1就是得到的最优值（自变量取值不用乘以-1）</p>
 * <p>需要计算则调用{@link #go()}</p>
 * <p>需要跟踪每次迭代则调用{@link #tracker(String, String, String, FileType)}</p>
 * <p>需要生成报告则调用{@link #report(String, String, String, FileType)}</p>
 * <p>鱼类大多生活在食物充足的地区。基于这一特性，对鱼类的行为进行仿真，寻找全局最优解，这是AFSA研究的基本思路。</p>
 * <p>人工鱼（以下简称鱼）主要包括如下行为:</p>
 * <ul>
 *     <li>觅食行为：{@link #AF_Prey(int)}</li>
 *     <li>聚群行为：{@link #AF_Swarm(int)}</li>
 *     <li>跟随行为：{@link #AF_Follow(int)}</li>
 * </ul>
 *
 * @author zyf
 */
@Algorithm
public class AFSA extends SwarmIntelligenceAlgorithm {

    /**
     * 每条鱼的当前位置
     */
    @TempParam(type = ParamType.DOUBLE_ARRAY_2, desc = "每条鱼的当前位置")
    private double[][] p;

    /**
     * 每条鱼当前函数值（适应度）
     */
    @TempParam(type = ParamType.DOUBLE_ARRAY_1, desc = "每条鱼当前函数值")
    private double[] f;

    /**
     * 鱼的视距
     */
    @BasicParam(type = ParamType.DOUBLE, desc = "鱼的视距")
    private double visual;

    /**
     * 鱼的步长
     */
    @BasicParam(type = ParamType.DOUBLE, desc = "鱼的步长")
    private double step;

    /**
     * 拥挤因子
     */
    @BasicParam(type = ParamType.DOUBLE, desc = "拥挤因子")
    private double delta;

    /**
     * 一次觅食尝试次数
     */
    @BasicParam(type = ParamType.INT, desc = "一次觅食尝试次数")
    private int tryNum;

    /**
     * 全局最优解对应的自变量取值
     */
    @TempParam(type = ParamType.DOUBLE_ARRAY_1, desc = "全局最优解对应的自变量取值")
    private double[] x;

    /**
     * 抽象算法构造函数，需要提供输入量，即提前初始化好{@link Input}对象
     *
     * @param input {@link Input}
     */
    public AFSA(Input input) {
        super(input);
        init();
    }

    private void init() {
        int n = getN();
        int D = getInput().getVarNum();
        this.p = new double[n][D];
        this.f = new double[n];
        this.x = new double[D];

        this.visual = 1;
        this.step = 0.2;
        this.delta = 0.3;
        this.tryNum = 10;

        for (int i = 0; i < n; i++) {
            for (int j = 0; j < getInput().getVarNum(); j++) {
                p[i][j] = getInput().randomValueInRange(j);
            }
            f[i] = getInput().getFunction().apply(p[i]);
        }
        x = p[0].clone();
    }

    /**
     * 每次迭代时扫描一次
     */
    private void scan() {
        int n = getN();
        int i;
        for (i = 0; i < n; i++) {
            // 聚群
            double[] p_swarm = AF_Swarm(i);
            // 跟随
            double[] p_follow = AF_Follow(i);
            // 觅食
            double[] p_prey = AF_Prey(i);
            // 选择三种策略最好的一种
            p[i] = getInput().chooseBestParam(p_swarm, p_follow, p_prey);
            f[i] = getInput().getFunction().apply(p[i]);
            if (!getInput().is_A_BetterThan_F(x, f[i])) x = p[i].clone();
        }
    }

    /**
     * 生成最优解
     */
    private void generateOptimization() {
        // 获取最优值
        double optimalOutput = getInput().getFunction().apply(x);
        setOutput(new Output(optimalOutput, x));
    }

    /**
     * 运行并计算结果
     */
    @Override
    public void go() {
        int t;
        for (t = 0; t < getIter(); t++) {
            scan();
        }
        generateOptimization();
    }

    /**
     * 运行并计算结果（按迭代逐步跟踪，自带运行，无需手动），如果目的是计算出结果，请使用{@link #go()}
     *
     * @param author   作者
     * @param message  备注
     * @param title    标题
     * @param fileType 文件类型 {@link FileType}
     * @return {@link Tracker}
     */
    @Override
    public Tracker tracker(String author, String message, String title, FileType fileType) {
        int t;
        Tracker tracker = new Tracker(this, author, message, title, fileType);
        tracker.startTracker();
        for (t = 0; t < getIter(); t++) {
            scan();
            tracker.trackOne(t + 1);
        }
        generateOptimization();
        tracker.endTracker();
        return tracker;
    }

    /**
     * 生成报告（要先手动运行才能生成报告）
     *
     * @param author   作者
     * @param message  备注
     * @param title    标题
     * @param fileType 文件类型 {@link FileType}
     * @return {@link Report}
     */
    @Override
    public Report report(String author, String message, String title, FileType fileType) {
        return new Report(this, author, message, title, fileType);
    }

    /**
     * <p>一条鱼的觅食行为</p>
     * <p>一般来说，鱼通过视觉感知水中食物的浓度来决定向哪移动，然后选择移动的方向。</p>
     * <p>
     * 设人工鱼当前位置为 p_i，在其感知范围内随机选择一个位置 p_k。
     * 如果选中的这个位置优于当前位置，那么就游到这个位置；
     * 如果选中的位置比当前位置差，那么就重新觅食。
     * 这样允许重新寻找的次数是{@link #tryNum}次，超过了这个次数，就随机选择一个点觅食。
     * </p>
     *
     * @param i 鱼的编号
     */
    private double[] AF_Prey(int i) {
        int D = getInput().getVarNum();
        int j, c;
        // 觅食得到的新点位
        double[] new_p = new double[D];
        // 用于判断是否尝试成功
        boolean trySuccess = false;
        // 用于产生随机数
        double r;
        for (c = 0; c < tryNum; c++) {
            r = Math.random() * 32767.0 / (32767.0 + 1.0);
            // 在可感知范围内随机选择一个点
            double[] p_k = new double[D];
            for (j = 0; j < D; j++) {
                p_k[j] = p[i][j] + visual * r;
            }
            // 判断选择的点是否更优
            if (getInput().is_A_BetterThan_B(p_k, p[i])) {
                double d = calcDistance(p[i], p_k);
                r = Math.random() * 32767.0 / (32767.0 + 1.0);
                for (j = 0; j < D; j++) {
                    new_p[j] = p[i][j] + (p_k[j] - p[i][j]) / d * step * r;
                }
                trySuccess = true;
                // 找到了就可以游过去了
                break;
            }
        }
        // 如果没有找到更优的点，那么就随便走一步
        if (!trySuccess) {
            for (j = 0; j < D; j++) {
                r = Math.random() * 32767.0 / (32767.0 + 1.0);
                new_p[j] = p[i][j] + step * r;
            }
        }
        return new_p;
    }

    /**
     * <p>一条鱼的聚群行为</p>
     * <p>鱼群在移动的过程中会自然地成群聚集，这是一种生活习惯，可以保证群体的存在，避免危险。</p>
     * <p>
     * 设人工鱼当前位置为 p_i，探索当前邻域内（即 d_ik < {@link #visual}）的伙伴数目 n_f 及中心位置 p_c。
     * 如果 n_f / n < {@link #delta} 且 f(p_c) < f(p_i)，
     * 则表明伙伴中心有较多的食物并且不太拥挤，于是朝伙伴的中心位置方向前游过去；否则正常觅食。
     * </p>
     *
     * @param i 鱼的编号
     */
    private double[] AF_Swarm(int i) {
        int n = getN();
        int D = getInput().getVarNum();
        int k, j;
        // 聚群得到的新点位
        double[] new_p = new double[D];
        // 用于产生随机数
        double r;
        // 同伴个数
        int n_f = 0;
        // 中心位置
        double[] p_c = new double[D];
        // 寻找同伴
        for (k = 0; k < n && k != i; k++) {
            if (calcDistance(p[i], p[k]) < visual) {
                // 检测到一个，n_f加一
                n_f++;
                p_c = ArrayUtils.plus(p_c, p[k]);
            }
        }
        // 计算中心位置
        ArrayUtils.numDiv(p_c, n_f);
        // 判断是否拥挤
        if (((double) n_f / n) < delta && getInput().is_A_BetterThan_B(p_c, p[i])) {
            // 如果不拥挤，那么就往这里靠拢
            double d = calcDistance(p[i], p_c);
            r = Math.random() * 32767.0 / (32767.0 + 1.0);
            for (j = 0; j < D; j++) {
                new_p[j] = p[i][j] + (p_c[j] - p[i][j]) / d * step * r;
            }
        } else {
            // 否则就正常觅食
            new_p = AF_Prey(i);
        }
        return new_p;
    }

    /**
     * <p>一条鱼的追随行为</p>
     * <p>在鱼群的移动过程中，当一条鱼或几条鱼找到食物时，附近的伙伴会迅速移动并到达食物。</p>
     * <p>
     * 设人工鱼当前位置为 p_i，探索当前邻域内（即 d_ik < {@link #visual}）的伙伴中适应度最优的伙伴（下面对这样的伙伴简称靓仔），
     * 然后像聚群行为那样，探索靓仔周围的情况。如果靓仔周围不拥挤，那么就往靓仔方向游过去；如果靓仔就是自己，那么就正常觅食。
     * </p>
     *
     * @param i 鱼的编号
     */
    private double[] AF_Follow(int i) {
        int n = getN();
        int D = getInput().getVarNum();
        int k, j;
        // 聚群得到的新点位
        double[] new_p = new double[D];
        // 最优点位索引（以下简称靓仔）
        int min_index = i;
        double r;
        // 寻找周围的靓仔
        for (k = 0; k < n && k != i; k++) {
            if (calcDistance(p[i], p[k]) < visual && f[k] < f[min_index]) {
                min_index = k;
            }
        }
        // 如果靓仔不是自己
        if (min_index != i) {
            // 靓仔的同伴个数
            int n_f = 0;
            // 搜寻靓仔周围的伙伴
            for (k = 0; k < n && k != i; k++) {
                if (calcDistance(p[min_index], p[k]) < visual) {
                    n_f++;
                }
            }
            if (((double) n_f / n) < delta) {
                // 如果不拥挤，那么就往这里靠拢
                double d = calcDistance(p[i], p[min_index]);
                r = Math.random() * 32767.0 / (32767.0 + 1.0);
                for (j = 0; j < D; j++) {
                    new_p[j] = p[i][j] + (p[min_index][j] - p[i][j]) / d * step * r;
                }
            }
        } else {
            // 如果靓仔是自己，那么就正常迷失
            new_p = AF_Prey(i);
        }
        return new_p;
    }

    /**
     * 计算两点之间的距离
     *
     * @param p1 点1
     * @param p2 点2
     * @return double
     */
    private double calcDistance(double[] p1, double[] p2) {
        int j;
        double d2 = 0.0;
        for (j = 0; j < getInput().getVarNum(); j++) {
            d2 += (p1[j] - p2[j]) * (p1[j] - p2[j]);
        }
        return Math.sqrt(d2);
    }

    public double getVisual() {
        return visual;
    }

    public void setVisual(double visual) {
        this.visual = visual;
    }

    public double getStep() {
        return step;
    }

    public void setStep(double step) {
        this.step = step;
    }

    public double getDelta() {
        return delta;
    }

    public void setDelta(double delta) {
        this.delta = delta;
    }

    public int getTryNum() {
        return tryNum;
    }

    public void setTryNum(int tryNum) {
        this.tryNum = tryNum;
    }

    public double[][] getP() {
        return p;
    }

    public double[] getF() {
        return f;
    }

    public double[] getX() {
        return x;
    }
}
